#include <windows.h>

#include "lib.h"

#include "slotconn.h"
#include "progconn.h"
#include "slotguts.h"
#include "slotopts.h"
#include "slotspin.h"
#include "sltsql.h"
#include "log.h"
#include "endian.h"

#include "stdncerr.h"
#include "opts.h"
#include "AUSlotsXMLTranslator.h"
//
// balance is included in EVERY message
//


SlotConnection::SlotConnection(ProgressiveServer* server, SOCKET client)
	: ProgressiveConnection(server, client)
{
	loggedOn = false;
	guts = (SlotGuts*)0;
    info = new Info();

    // kharmon.  11-15-06.  Zero out buffers before using them.
	msglen = 0;
	memset(msgbuf, 0, sizeof(msgbuf));
}

SlotConnection::~SlotConnection()
{
	trackLogoutSession();	// CSN-7075: New Authentication
	
	removeCasinoSpecific();
    delete guts;
    //delete info;
}


void
SlotConnection::SendInt32(int32 data)
{
    msglen += Endian::SendInt4(data, &msgbuf[msglen]);
}

void
SlotConnection::SendInt64(int64 data)
{
    msglen += Endian::SendInt8(data, &msgbuf[msglen]);
}

void
SlotConnection::SendUint16(uint16 data)
{
    msglen += Endian::SendInt2(data, &msgbuf[msglen]);
}



void
SlotConnection::SendUint8(uint8 data)
{
    msglen += Endian::SendByte(data, &msgbuf[msglen]);
}

uint8
SlotConnection::SendString( char* s )
{
    uint8 len = 1;
	if (s==NULL)
	{
		SendUint8(0);
	}
    else
    {
    	len = strlen(s);
    	if (len >= (sizeof( msgbuf ) - msglen))
        {
            len = 0;
        }
        else
        {
        	SendUint8(len);
        	for (int i=0; i<len; i++)
            {
        		SendUint8(s[i]);
            }
        }
    }

	return len+1;
}



void
SlotConnection::msgStart()
{
	msglen = 0;
}

void
SlotConnection::msgAdd(uint8 Byte)
{
	msgbuf[msglen++] = Byte;
}

uint8*
SlotConnection::msgGetBuf()
{
	return msgbuf + msglen;
}

void
SlotConnection::msgReleaseBuf(int32 Amt)
{
	msglen += Amt;
}

void
SlotConnection::msgEnd()
{
	ASSERT(msglen + oused + 2 < CONNECT_OBUF);

	msglen = encode(msgbuf, obuf + oused + 2, msglen);
	msglen += 2;	// include msg len bytes
	obuf[oused] = (uint8)(msglen & 0xff);
	obuf[oused + 1] = (uint8)((msglen >> 8) & 0xff);
	oused += msglen;
}

void
SlotConnection::sendMalfunctionMsg()
{
	msgStart();
	msgAdd((uint8)'E');
	msgAdd(0x04);
	msgEnd();
	Log::msgWarning("SlotConnection - sending malfunction message");
}

// FogBugz 753 - GChapman
// Added new error message for malfunction condition during login.
void
SlotConnection::sendLoginMalfunctionMsg()
{
	msgStart();
	msgAdd((uint8)'E');
	msgAdd(LOGINERR_MALFUNCTION);
	msgEnd();
	Log::msgWarning("SlotConnection - sending login malfunction message");
}

void
SlotConnection::sendStopMsg()
{
	msgStart();
	msgAdd((uint8)'!');
	msgEnd();
}

bool8
SlotConnection::handleJackpotMsg()
{
    int32	jackpot;
	int32	rate;

	getProgressiveData(&jackpot, &rate);

	msgStart();
	SendUint8('J');
	SendInt32(jackpot);
	SendInt32(rate);
	msgEnd();

    return true;
}

// CSN-5756: with the new korean team game (Saturn GP) we need to support fixed jackpot amounts
bool8
SlotConnection::handleFixedJackpotMsg(int32 JackpotValue)
{
	msgStart();
	SendUint8('J');
	SendInt32(JackpotValue);
	SendInt32(0);
	msgEnd();

    return true;
}

bool8
SlotConnection::handleSavedGameMsg()
{
	if(guts->loadSavedGame())
	{
		msgStart();
		SendUint8('V');
        SendInt32(guts->getInfo()->betperline);

		SlotSpin *spin = guts->getSlotSpin();

		SendUint8(spin->getStopCount());
        SendUint8(spin->getGameModeId());

		for ( uint8 reelid = 0; reelid < spin->getStopCount(); reelid++ )
		{
			SendUint8(spin->getStops()[reelid]);
		}

		/**
		 * Write the lines.
		 */
		// CGC 20111202. If it is a third party game (Genesis) we don't send the lines here
		// (Because the output buffer can overflow due to the big amount of information)
		if (!guts->isThirdPartyGame())
		{
			SendUint8(spin->getSpinLineCount());
			/**
			 * Lines are 1 based.
			 */
			for ( int lineid = 1; lineid <= spin->getSpinLineCount(); lineid++ )
			{
				const SpinLine *line = spin->getSpinLineByLineId(lineid);

				SendUint8(line->lineid);
				SendUint8(line->payouttypeid);
				SendInt32(line->basepayoutamount );
				SendInt32(line->totalmultiplier);
				SendInt32(line->totallinepayoutamount);
				SendUint8(line->jackpot);

				SendUint8(line->m_resultNotes.size());

				for ( size_t noteid = 0; noteid < line->m_resultNotes.size(); noteid++)
				{
					SendUint8(line->m_resultNotes[noteid]);
				}
			}
		}
		else
		{
			// Third party game: 0 lines
			SendUint8(0);
		}

		/**
		 * Write the scatters.
		 */
		SendUint8(spin->getNumScatters());

		for ( int scatterIndex = 0; scatterIndex < spin->getNumScatters(); scatterIndex++ )
		{
			const SlotScatter *pScatter = spin->getScatter(scatterIndex);

            if ( pScatter )
            {
				SendUint8(pScatter->scatterid);
				SendInt32(pScatter->scatterposition);
				SendUint8(pScatter->payouttypeid);
                SendInt32(pScatter->basepayoutamount );
                SendInt32(pScatter->totalmultiplier);
				SendInt32(pScatter->totalscatterpayoutamount);
				SendUint8(pScatter->jackpot);
				SendInt32(pScatter->freespins);

                SendUint8(pScatter->m_resultNotes.size());

                for (size_t noteid = 0; noteid < pScatter->m_resultNotes.size(); noteid++)
                {
                    SendUint8(pScatter->m_resultNotes[noteid]);
                }
            }
            else
            {
                ASSERT(false);
				SendInt32(0);
				SendUint8(0);
				SendInt32(0);
				SendUint8(0);
            }
		}

		/**
		 * Write the bonus games.
		 */
		SendUint8(spin->getNumBonusGames());
		for ( int bonusIndex = 0; bonusIndex < spin->getNumBonusGames(); bonusIndex++ )
		{

			const BonusGame* pBonus = spin->getBonusGame(bonusIndex);

            if ( pBonus )
            {
				SendUint8(pBonus->bonusgametype);
				SendUint8(pBonus->bonusgameindex);
				SendInt32(pBonus->basepayoutamount);
                SendInt32(pBonus->totalmultiplier );
                SendInt32(pBonus->totalbonuspayoutamount);
				SendInt32(pBonus->freespins);
            }
            else
            {
                ASSERT(false);
				SendUint8(0);
				SendUint8(0);
				SendInt32(0);
				SendInt32(0);
				SendInt32(0);
				SendInt32(0);
            }
		}

		msgEnd();
	} //if there is a save dgame

	return true;
}

bool8
SlotConnection::handleGutsMsg()
{
    bool8 rc;

    rc = handleGutsDenominationsMsg();

    if ( rc )
    {
        rc = handleGutsPaylinesMsg();
    }

    return rc;
}

bool8
SlotConnection::handleGutsIconNameMsg()
{
    ///@todo Write this method.
    return true;
}

bool8
SlotConnection::handleGutsReelsMsg()
{
    msgStart();
    SendUint8('R');
    SendUint8(guts->getWheelhouseCount() );

    for ( uint8 k = 0; k < guts->getWheelhouseCount(); k++ )
    {
        SendUint8(guts->getReelCount(k));

        for ( uint8 i = 0; i < guts->getReelCount(k); i++ )
        {
            SendUint8(guts->getIconCount(k,i));

            for ( uint8 j = 0; j < guts->getIconCount(k,i); j++ )
            {
                SendUint8( guts->getIcon(k, i, j)->iconid );
            }
        }
    }

    msgEnd();

    return true;
}

bool8
SlotConnection::handleGutsDenominationsMsg()
{
    msgStart();
    SendUint8('D');

	uint8 nDenomCount = 0;

    for ( uint8 nDenomination = 0; nDenomination < guts->getDenominationCount(); nDenomination++ )
    {
        SlotGameDenomination* pDenomination = guts->getDenomination(nDenomination);

		if ( guts->getInfo()->machineSubID == pDenomination->m_machineSubID )
		{
			nDenomCount++;
		}
    }

    SendUint8(nDenomCount);

    for ( nDenomination = 0; nDenomination < guts->getDenominationCount(); nDenomination++ )
    {
        SlotGameDenomination* pDenomination = guts->getDenomination(nDenomination);

		if ( guts->getInfo()->machineSubID == pDenomination->m_machineSubID )
		{
	        SendUint16(pDenomination->m_machineSubID);
		    SendUint16(pDenomination->m_creditValue);
			SendUint8(pDenomination->m_isDefault);
		}
    }

    msgEnd();

    return true;
}

bool8
SlotConnection::handleGutsPaylinesMsg()
{
    msgStart();
    SendUint8('Y');
    SendUint8(guts->getPaylineCount());
    SendUint8(guts->getReelCount());

    for ( uint8 paylineNum = 1; paylineNum <= guts->getPaylineCount(); paylineNum++ )
    {

		//for ( uint8 reelNum = 0; reelNum < guts->getReelCount(); reelNum++ )
		// We only have a gutsid with more than 5 reels: Football Frenzy. And even with this one we want to send to the client only 5 reels,
		// so fix this value to a maximum lf 5
		uint8 iReelNum = guts->getReelCount() > 5 ? 5 : guts->getReelCount();
		for ( uint8 reelNum = 0; reelNum < iReelNum; reelNum++ )
        {
            uint8 position = guts->getPaylinePosition(paylineNum, reelNum);
            SendUint8(position);
        }
    }

    msgEnd();

    return true;
}

/**
 * send the bonus games
 */
bool8
SlotConnection::handleBonusGameMsg()
{
	int32 numGames = 0;

	if ( guts->getSlotSpin() )
	{
		numGames = guts->getSlotSpin()->getNumBonusGames();
	}

	msgStart();
	SendUint8('B');
	SendInt32(numGames);

	for (int32 i=0; i<numGames; ++i)
	{
		const BonusGame* pBonus = guts->getSlotSpin()->getBonusGame(i);

		SendUint8(pBonus->bonusgametype);
		SendUint8(pBonus->bonusgameindex);
		SendInt32(pBonus->basepayoutamount);
		SendInt32(pBonus->totalmultiplier );
		SendInt32(pBonus->totalbonuspayoutamount);
		SendInt32(pBonus->freespins);
	}
	msgEnd();
	return true;
}


#ifdef ALLOW_SPIN_TESTING
bool8
SlotConnection::handleSpinTestMessage()
{
	bool bSendAck = false;
	bool bAckSuccess = false;

	msgStart();

	CSpinTestMessage* pMsg = (CSpinTestMessage*)msgGetBuf();

	ASSERT( pMsg->m_byMessageCode == 'T' );

	if ( pMsg->m_byMessageSubCode == CSpinTestMessage::mscInitialize )
	{
		ASSERT( guts == NULL );

		CSpinTestInitializeMessage* pInitMsg = (CSpinTestInitializeMessage*) pMsg;

		m_SpinTestData.Initialize( pInitMsg->m_byMaxGameMode, pInitMsg->m_byMaxPayoutType, pInitMsg->m_byMaxBonusGameType, pInitMsg->m_bySubstNoteID, pInitMsg->m_byNumClientDataSets );

		info->machineID = pInitMsg->m_byMachineID;
		info->machineSubID = pInitMsg->m_byMachineSubID;

		guts = SlotGuts::createGuts(info->machineID);

		bSendAck = true;

		if ( guts && guts->load(info) )
		{
			setProgressiveJackpotID(guts->getJackpotId());
			setProgressiveOn(guts->isProgressive());

			info->forMoney = guts->canMakeSpinTestProgressiveBet();
			pProgServ->loadJackpot(guts->getJackpotId(), getForRealFlag(), getProgressiveFixedAmountMultiplier());

			bAckSuccess = true;
		}
		else
		{
			Log::msgError("Spin Test: bad slot guts: %d  ", info->machineID);

			delete guts;
			guts = (SlotGuts *) 0;
			info->betperline = 0;
		}
	}
	else if ( pMsg->m_byMessageSubCode == CSpinTestMessage::mscClientData )
	{
		CSpinTestClientDataMessage* pClientDataMsg = (CSpinTestClientDataMessage*) pMsg;

		bSendAck = true;

		if ( m_SpinTestData.AddClientDataSet( pClientDataMsg ) )
		{
			bAckSuccess = true;
		}
	}
	else if ( pMsg->m_byMessageSubCode == CSpinTestMessage::mscExecuteSpins )
	{
		CSpinTestExecuteSpinsMessage* pExecMsg = (CSpinTestExecuteSpinsMessage*) pMsg;

		if ( guts )
		{
			executeSpinTest( pExecMsg->m_nNumSpins, pExecMsg->m_nNumLines, pExecMsg->m_nBetPerLine );
		}
		else
		{
			bSendAck = true;
		}
	}
	else if ( pMsg->m_byMessageSubCode == CSpinTestMessage::mscGetGameModeSummaryData )
	{
		CSpinTestGetGameModeSummaryDataMessage* pGetDataMsg = (CSpinTestGetGameModeSummaryDataMessage*) pMsg;

		m_SpinTestData.SendGameModeSummaryData( this, pGetDataMsg->m_byGameMode );
	}
	else if ( pMsg->m_byMessageSubCode == CSpinTestMessage::mscGetPayoutTypeDetailData )
	{
		CSpinTestGetPayoutTypeDetailDataMessage* pGetDataMsg = (CSpinTestGetPayoutTypeDetailDataMessage*) pMsg;

		m_SpinTestData.SendPayoutTypeDetailData( this, pGetDataMsg->m_byGameMode, pGetDataMsg->m_byPayoutType );
	}
	else if ( pMsg->m_byMessageSubCode == CSpinTestMessage::mscGetBonusGameTypeDetailData )
	{
		CSpinTestGetBonusGameTypeDetailDataMessage* pGetDataMsg = (CSpinTestGetBonusGameTypeDetailDataMessage*) pMsg;

		m_SpinTestData.SendBonusGameTypeDetailData( this, pGetDataMsg->m_byBonusGameType );
	}

	if ( bSendAck )
	{
		msgStart();

		// Use placement new to call the constructor using the msg buffer for "this".
		// Note:  DO NOT call delete since we aren't actually allocating any memory.
		// We are simply constructing a CSpinTestAckMessage object inside
		// a buffer that was already allocated and managed elsewhere.
		#pragma push_macro("new")
		#undef new
		CSpinTestAckMessage* pAckMsg = new( msgGetBuf() ) CSpinTestAckMessage;
		#pragma pop_macro("new")


		pAckMsg->m_bSuccess = bAckSuccess;

		msgReleaseBuf( sizeof( *pAckMsg ) );

		msgEnd();
	}

	return true;
}

void
SlotConnection::executeSpinTest( uint32 nNumSpins, uint32 nLines, uint32 nBetPerLine )
{
	m_SpinTestData.Reset();

	info->betperline = nBetPerLine;

	uint32 nNumLinesToPlay = nLines * nNumSpins;

	// Do a whole bunch of spins, but don't stop if we are in a feature.
	while ( m_SpinTestData.GetNumPaidLinesPlayed() < nNumLinesToPlay || guts->getFreeSpins() )
	{
		int32 payout;
		SlotSpin *spin;
		int32 multibankPayout = 0;
		int32 multibankBetCents = 0;
		bool8 bAutoplay = false;

		if ( guts->isProgressive())
		{
		   if ( guts->getFreeSpins() == 0 )
		   {
			   int32 nContribution = 0;

			   if ( guts->getProgressiveContribution( nBetPerLine, nLines, nContribution, multibankBetCents ) )
			   {
				   placeProgressiveContribution( nContribution, multibankBetCents );
			   }
			   else
			   {
				   placeProgressiveBet(nBetPerLine * nLines, nLines, multibankBetCents);
			   }
			}
		}

		guts->spin(multibankBetCents, nLines, 0, bAutoplay);

		while ( !guts->getGameCanComplete() )
		{
			m_SpinTestData.SendClientData( guts );
		}

		/**
		 * Go through the lines looking for progressive winners.
		 */

		spin = guts->getSlotSpin();

		for ( uint8 lineidx = 0; lineidx < spin->getSpinLineCount(); lineidx++ )
		{
			const SpinLine *spinLine = spin->getSpinLineAt(lineidx);

			if ( spinLine->jackpot != 0 && isProgressiveOn() )
			{
				pProgServ->setProgressiveWinner(guts->getJackpotId(), 100, guts->isForMoney(), getProgressiveFixedAmountMultiplier(), &payout, &multibankPayout);
				spin->setJackpotAmount(spinLine->lineid, payout);
				spin->setMultibankPayout(multibankPayout);
				guts->setJackpotAmount(payout);
			}

		}

		SlotScatter* pScatter = NULL;
		for ( uint8 scatteridx = 0; scatteridx < spin->getNumScatters(); scatteridx++ )
		{
			pScatter = spin->getScatter( scatteridx );

			if ( pScatter->jackpot != 0 && isProgressiveOn() )
			{
				pProgServ->setProgressiveWinner(guts->getJackpotId(), 100, guts->isForMoney(), getProgressiveFixedAmountMultiplier(), &payout, &multibankPayout);
				pScatter->basepayoutamount = pScatter->totalscatterpayoutamount = payout;
				pScatter->totalmultiplier = 1;
				spin->setMultibankPayout(multibankPayout);
				guts->setJackpotAmount(payout);
			}
		}

		bool bPaidSpinCompleted = true;

		if ( guts->getFreeSpins() )
		{
			bPaidSpinCompleted = false;
		}

		m_SpinTestData.AccumulateSpinResults( nLines, nBetPerLine, bPaidSpinCompleted, spin );

		guts->resetForNewGame();
	}

	m_SpinTestData.SendSummaryData( this );
}
#endif // ALLOW_SPIN_TESTING

bool8
SlotConnection::handleLoginMsg()
{
    if (isServerStopping())
    {
        sendStopMsg();
    }
    else
    {
        if (!loggedOn)
        {
			if (!trackGetPlayerLimits())
			{
				msgStart();
				SendUint8('N');
				msgEnd();

				info->betperline = 0;

				Log::msgWarning("Login: bad player limit. ");
			}

            if (!trackGetBalance())
            {
                msgStart();
                SendUint8('N');
                msgEnd();

                info->betperline = 0;

				Log::msgWarning("Login: bad player balance");
            }
            else
            {
                guts = SlotGuts::createGuts(info->machineID);

                if ( guts && guts->load(info) )
                {
					this->setProgressiveJackpotID(guts->getJackpotId());
					this->setProgressiveOn(guts->isProgressive());
                    this->pProgServ->loadJackpot(guts->getJackpotId(), this->getForRealFlag(), this->getProgressiveFixedAmountMultiplier());

                    handleGutsMsg();
                    handleSubgameMsg();
                    handleSavedGameMsg();
					if (guts->isProgressive())
					{
						handleJackpotMsg();
					}
					else
					{
						// CSN-5756: with the new korean team game (Saturn GP) we need to support fixed jackpot amounts 
						//           THIS IS, NOT PROGRESSIVE JACKPOTS
						int32 iJackpotValue = guts->getFixedJackpotValue();
						handleFixedJackpotMsg(iJackpotValue);
					}

                    // kharmon.  12-14-05.  #8140.  This message needs to be sent after the 
                    // previous messages because even though it was being sent during the call
                    // to handleGutsMsg(), the client USUALLY processed it last.  BUT, if the 
                    // network broke these messages up just right, it would INTERMITTENTLY get 
                    // processed out of the normal order...and when this rare event happened
                    // some games (e.g., Super Diamond Mine) would break.
                    // 
                    // SO, since it NORMALLY gets processed last anyway, let's just make sure 
                    // that this ALWAYS happens by sending it last.
                    //
                    // Note that the reason it normally got processed last is because it was posted
                    // to the event queue, whereas the other messages are handled immediately and thus
                    // would complete before the Guts msg got pulled back off the event queue.
                    handleGutsReelsMsg();



                    msgStart();
                    SendUint8('L');
                    SendInt32(info->balance);

                    msgAdd(guts->isProgressive());
                    msgEnd();

                    loggedOn = true;

                    if(guts->isMalfunction())
					{
						sendLoginMalfunctionMsg();
					}

				}
                else
                {
                    Log::msgError("Login: bad slot guts: %d  ", info->machineID);

                    msgStart();
                    msgAdd((uint8)'N');
                    msgEnd();

                    delete guts;
                    guts = (SlotGuts *) 0;
                    info->betperline = 0;
                }
            }
        }
        else
        {
            Log::msgWarning("Login: attempt to re-log on  ");

			// REVISIT: SNA Vulnerability: #2002120901
            msgStart();
            msgAdd((uint8)'N');
            msgEnd();
        }
    }

    return true;
}


bool8
SlotConnection::handleSubgameMsg()
{
	SubGame *subGame = guts->getSubGame();

    uint8	clientonlydatasize = subGame->getClientOnlyDataSize();
    uint8	datasize = guts->getSubGameSize();

    // The code to send the subgame data to the client
    // has been modified.  We now pass both the id and the
    // data and rebuild the subgamedata structure on the other side.
    // We had problems where "gaps" in the initialized sub game data
    // would screw up games because the the client assumed there
    // would be no gaps.  Now, we simply rebuild the sub game strcuture
    // on the client (passing only data that is not "server only").
    msgStart();
    SendUint8('G');
    SendUint8(clientonlydatasize);

    int32 id;
    int32 data;

    for (uint8 i = 0; i < datasize; i++ )
    {
        if ( !subGame->isServerOnlyAt(i) )
        {
            id = subGame->getIdAt(i);
            data = subGame->getDataAt(i);

            SendInt32(id);
            SendInt32(data);
        }
    }

    msgEnd();
    return true;
}

/*//////////////////////////////////
	HANDLESPINMSG
	handle the player spin request
//////////////////////////////////*/
bool8
SlotConnection::handleSpinMsg()
{
	//rule1: gotta be logged on
    if (!loggedOn)
    {
        Log::msgWarning("Spin: not logged on...invalid bet  ");
        msgStart();
        SendUint8((uint8)'E');
        SendUint8((uint8)0x10);
        msgEnd();
		return true;
    }

	//rule2: server cannot be going down
	if (isServerStopping())
	{
		sendStopMsg();
		return true;
	}

	// [AO 2008-01-16] HA: verify the user is still logged on
	if (!ValidateSession())
		return true;

	//now parse some necessary message parameters
	int32 msgBufIdx = 1;
	uint8 lineCount = msgbuf[msgBufIdx++];
    Endian::ReceiveInt4(&info->betperline, &msgbuf[msgBufIdx]);
    msgBufIdx+=4;
    bool8 bAutoplay = msgbuf[msgBufIdx++];

	//look at the player balance in the database
	trackGetBalance();

    bool bBetPerLineInvalid = true;

    // Ensure the credit value passed from the client
    // is valid for this machine.
    for ( uint8 nDenomination = 0; nDenomination < guts->getDenominationCount(); nDenomination++ )
    {
        SlotGameDenomination* pDenomination = guts->getDenomination(nDenomination);

        if ( pDenomination->m_creditValue == info->betperline && info->machineSubID == pDenomination->m_machineSubID )
        {
            bBetPerLineInvalid = false;
            break;
        }
    }

    if ( bBetPerLineInvalid )
    {
        // There is a problem with the credits being sent from the client.
		Log::msgWarning("Spin: invalid credits");
		msgStart();
		msgAdd((uint8)'$');
		msgEnd();
		return true;
    }

	//rule3: must have enough balance to cover the bet on a paid spin
	if (guts->getFreeSpins() == 0 && ( info->betperline*lineCount > info->balance ) )
    {
		// player balance is too low
		Log::msgWarning("Spin: out of money  ");
		msgStart();
		msgAdd((uint8)'$');
		msgEnd();
		return true;
    }

	// rule4: machine must be in a spinnable state (i.e. not in a bonus game)
	if ( !guts->canSpin() )
    {
		Log::msgWarning("Spin: bonus game in progress  ");
		msgStart();
		msgAdd((uint8)'E');
		msgAdd((uint8)0);
		msgEnd();
		return true;
    }

	// rule4: machine must be in a spinnable state (i.e. not in a bonus game)
	if ( !guts->canSpin() )
    {
		Log::msgWarning("Spin: bonus game in progress  ");
		msgStart();
		msgAdd((uint8)'E');
		msgAdd((uint8)0);
		msgEnd();
		return true;
    }

	//rule5: cannot play if you have a malfunction
	if (guts->isMalfunction())
    {
		Log::msgWarning("Spin:Already Malfunction");
		sendMalfunctionMsg();
		return true;
	}

	//rule6: num lines bet must be equal or less than guts number of lines  // CGC: 20100610
	if ((lineCount > guts->getPaylineCount()) || (lineCount < 0))
    {
		// There is a problem with the credits being sent from the client.
		Log::msgWarning("Spin: invalid number of lines");
		msgStart();
		msgAdd((uint8)'$');
		msgEnd();
		return true;
	}

#ifdef ALLOW_FORCE_BONUS_GAMES
	if ( msgbuf[msgBufIdx] == 'B' )
	{
		guts->setForBonusGameOnSpin();
	}
#endif

	//spin it baby!
	executeSpinMsg(lineCount,bAutoplay);

	//success
    return true;
}


void
SlotConnection::executeSpinMsg(uint8 lineCount, bool8 bAutoplay)
{
    int32 payout;
    SlotSpin *spin;
	bool8 MBerror = false;
	bool8 jackpotsuccess = true;
    int32 multibankPayout = 0;
    int32 multibankBetCents = 0;

    if ( guts->isProgressive())
    {
       if ( guts->getFreeSpins() == 0 )
       {
            int32 nContribution = 0;

            if ( guts->getProgressiveContribution(info->betperline, lineCount, nContribution, multibankBetCents) )
            {
                if( !placeProgressiveContribution(nContribution, multibankBetCents))
                {
                    //Can't make progressive bet - refund the money and error out
                    MBerror = true;
                    Log::msgWarning("Spin: Error placing multibank bet (out of funds?)");
                    msgStart();
                    msgAdd((uint8)'E');
                    msgAdd((uint8)0);
                    msgEnd();

                }
            }
            else
            {
	           if( !placeProgressiveBet(info->betperline* lineCount, lineCount, multibankBetCents))
	           {
			        //Can't make progressive bet - refund the money and error out
			        MBerror = true;
			        Log::msgWarning("Spin: Error placing multibank bet (out of funds?)");
			        msgStart();
			        msgAdd((uint8)'E');
			        msgAdd((uint8)0);
			        msgEnd();

	           }
	       }
	    }
    }


    if(!MBerror)
    {
	    guts->spin(multibankBetCents, lineCount, 0, bAutoplay);

	    /**
	     * Go through the lines looking for progressive winners.
	     */

        spin = guts->getSlotSpin();

		for ( uint8 lineidx = 0; lineidx < spin->getSpinLineCount(); lineidx++ )
		{
			const SpinLine *spinLine = spin->getSpinLineAt(lineidx);

			//Josh 2/28 DEBUG ONLY - MAKES PROGRESSIVE JACKPOTS!
//#define MAKEPROGJACKPOTS
#ifdef MAKEPROGJACKPOTS
			if (lineidx == 0)
			{
				spin->setJackpotAmount(spinLine->lineid,0); //FORCE A JACKPOT
			}
#endif MAKEPROGJACKPOTS

			if ( spinLine->jackpot != 0 && this->isProgressiveOn() )
			{
				jackpotsuccess = this->pProgServ->setProgressiveWinner(guts->getJackpotId(), 100, guts->isForMoney(), getProgressiveFixedAmountMultiplier(), &payout, &multibankPayout);
				spin->setJackpotAmount(spinLine->lineid, payout);
				spin->setMultibankPayout(multibankPayout);
				guts->setJackpotAmount(payout);
			}

		}

        SlotScatter* pScatter = NULL;
        for ( uint8 scatteridx = 0; scatteridx < spin->getNumScatters(); scatteridx++ )
        {
            pScatter = spin->getScatter( scatteridx );

            if ( pScatter->jackpot != 0 && isProgressiveOn() )
            {
				jackpotsuccess = this->pProgServ->setProgressiveWinner(guts->getJackpotId(), 100, guts->isForMoney(), getProgressiveFixedAmountMultiplier(), &payout, &multibankPayout);
                pScatter->basepayoutamount = pScatter->totalscatterpayoutamount = payout;
                pScatter->totalmultiplier = 1;
                spin->setMultibankPayout(multibankPayout);
                guts->setJackpotAmount(payout);
            }
        }

    /**
     * Write the winners.
     */
	    if ( jackpotsuccess && guts->saveGame() )
	    {

		    if ( guts->saveSpin() )
		    {
	 		    guts->saveSubGameData();
			    handleSubgameMsg();
				packSpin();

			    if ( !guts->isMalfunction() )
			    {
				    if ( guts->getGameCanComplete() )
				    {
					    guts->completeGame();
				    }
			    }
				else
				{
					sendMalfunctionMsg();
				}
		    }
		    else
		    {
			    // Unable to save the spins
			    //
			    msgStart();
			    msgAdd((uint8)'E');
			    msgAdd((uint8)0);
			    msgEnd();
			    ASSERT(0);
		    }
	    }
	    else
	    {
		    // Unable to save the game
		    //
		    msgStart();
		    msgAdd((uint8)'E');
		    msgAdd((uint8)0);
		    msgEnd();
		    ASSERT(0);
	    }
    }
}

void
SlotConnection::packSpin()
{

    msgStart();
    SendUint8('S');
    SendInt32(info->balance);

    SlotSpin *spin = guts->getSlotSpin();

    SendUint8(spin->getStopCount());
    SendUint8(spin->getGameModeId());

    for ( uint8 reelid = 0; reelid < spin->getStopCount(); reelid++ )
    {
        SendUint8(spin->getStops()[reelid]);
    }

    /**
     * Write the lines.
     */
    SendUint8(spin->getSpinLineCount());
    /**
     * Lines are 1 based.
     */
    for ( int lineid = 1; lineid <= spin->getSpinLineCount(); lineid++ )
    {
        const SpinLine *line = spin->getSpinLineByLineId(lineid);

        SendUint8(line->lineid);
        SendUint8(line->payouttypeid);
        SendInt32(line->basepayoutamount );
        SendInt32(line->totalmultiplier);
        SendInt32(line->totallinepayoutamount);
        SendUint8(line->jackpot);

        SendUint8(line->m_resultNotes.size());

        for (size_t noteid = 0; noteid < line->m_resultNotes.size(); noteid++)
        {
            SendUint8(line->m_resultNotes[noteid]);
        }
    }

	/**
	 * Write the scatters.
	 */
	SendUint8(spin->getNumScatters());

	for ( int scatterIndex = 0; scatterIndex < spin->getNumScatters(); scatterIndex++ )
	{
		const SlotScatter *pScatter = spin->getScatter(scatterIndex);

        if ( pScatter )
        {
            SendUint8(pScatter->scatterid);
			SendInt32(pScatter->scatterposition);
			SendUint8(pScatter->payouttypeid);
            SendInt32(pScatter->basepayoutamount );
            SendInt32(pScatter->totalmultiplier);
			SendInt32(pScatter->totalscatterpayoutamount);
			SendUint8(pScatter->jackpot);
            SendInt32(pScatter->freespins);

            SendUint8(pScatter->m_resultNotes.size());

            for (size_t noteid = 0; noteid < pScatter->m_resultNotes.size(); noteid++)
            {
                SendUint8(pScatter->m_resultNotes[noteid]);
            }
        }
        else
        {
            ASSERT(false);
			SendInt32(0);
			SendUint8(0);
			SendInt32(0);
			SendUint8(0);
        }
	}
	// For a regular spin message we will send down no bonus game information.
	// Bonus game information is sent with a spin only in the saved game message.
	SendUint8(0);


    msgEnd();

}

bool8
SlotConnection::handleClientErrorMsg()
{
	String errFormat;
	String errMsg;

	int32 i = 1;

	i += errMsg.deserialize(msgbuf + i);

	errFormat = "Client Error Message\n\n"
		        "Paid Game number:    %d\n"
				"Gut ID:              %d\n"
				"Error Text:\n";

	errFormat +=	errMsg;

	Log::msgError(errFormat,guts->getReferenceGameNum(),guts->getGutsId());

	return true;
}

bool8
SlotConnection::handleClientDataMsg()
{
	uint8 dataType;
    int16 dataCount;
    int32 *data;
    int32 i;

	// [AO 2008-01-16] HA: verify the user is still logged on
	if (!ValidateSession())
		return true;

    // kharmon.  05-02-07.  Occasionally we come across corrupt saved games where the subgame data
    // says that there's a bonus game in progress, but the save game data doesn't reconstitute into
    // a valid spin.  SO, as near as I can tell, we should never get here without a valid spin, so
    // send back an error message if we detect this situation, INSTEAD of crashing due to accessing
    // this null pointer down in the bonus game code.
    // kharmon.  07-20-07.  #19494. Added extra condition.  Lion's Lair was getting into the error 
    // code when it shouldn't have.
    if(guts->getSlotSpin()==NULL && guts->getGameCanComplete()==false && guts->isBonusGameInProgress()==true)
    {
		msgStart();
		msgAdd((uint8)'E');
		msgAdd(0x0C);
        //SendInt32((int32) guts->getFeature()->getGameNum());
		SendInt64((int64) guts->getFeature()->getGameNum());
		msgEnd();
        return true;
    }
    /*  Collect the bonus game data */

	dataType = msgbuf[1];
    Endian::ReceiveInt2(&dataCount, &msgbuf[2]);

	if ( dataCount < 1 )
	{
		dataCount = 1;
		data = new int32 [1];
		data[0] = 0;
	}
	else
	{
		data = new int32 [dataCount];

		for ( i = 0; i < dataCount; i++ )
		{
			Endian::ReceiveInt4(&data[i], &msgbuf[ i * 4 + 4]);
		}
	}

    /* Let the guts do whatever it needs to it */
    bool8 bReply = guts->processClientData(dataType, dataCount, data);

    delete [] data;

	if ( bReply )
	{
		/* Send the subgame message in case the data changed */
		guts->saveBonusGames();
		guts->saveSubGameData();
		handleBonusGameMsg();
		handleSubgameMsg();
	}

    if ( guts->getGameCanComplete() && guts->getSlotSpin())
    {
	    guts->completeGame();
	}
    return true;
}

bool8
SlotConnection::handleAnsiLoginMsg()
{
	int32 err = 0;

	if (!parseAnsiCasinoLogin(8, &err))
	{
		msgStart();
		msgAdd((uint8)'E');
		msgAdd((int32) err);
		msgEnd();

		return true;
	}

	return handleLoginMsg();
}

bool8
SlotConnection::handleUnicodeLoginMsg()
{
	int32 err = 0;

	if (!parseUnicodeCasinoLogin(9, &err))
	{
		msgStart();
		msgAdd((uint8)'E');
		msgAdd((int32) err);
		msgEnd();

		return true;
	}

	return handleLoginMsg();

}

bool8
SlotConnection::handleUnknownMsg()
{
	Log::msgWarning("Connection::getResponse:  Unknown input (0x%02x)  ",
		msgbuf[0]);

	// Server Network Audit: Vulnerability #2002120901
	//
#ifdef DEBUG				// FIX - SNA:Vulnerability #2002120901
	msgStart();
	msgAdd((uint8)'N');
	msgEnd();
#endif						// FIX - SNA:Vulnerability #2002120901

	return true;
}

bool8
SlotConnection::getResponse()
{
	int32	ieat	= 0;
	uint32	remain	= iused;
	uint8 *	src		= ibuf;
    bool8	rc		= false;
	uint32	message_length;
	uint32	decode_length;

	// [AO 2007-10-01] We can send just one reply for certain repeating messages.
	// We're not handling partial buffers and incomplete messages but that's OK.
	char last_message = 0;

	// Slots utilizes a two byte size field, hence we need at least two bytes
	// for any given message.
	while (remain >= 2)
	{
		message_length = src[0] + (src[1] << 8);

		// Reject invalid message lengths of zero, one, or two - because they
		// do not contain payload data.
		if (message_length <= 2)
		{
			ieat	+= 2;
			remain	-= 2;
			src		+= 2;
			continue;
		}

		// Reject invalid message lengths which exceed the length of our
		// receive buffer.
		if (message_length > INMAX)
		{
			DEBUGMSG(("SlotConnection::getResponse:"
				"  message length exceeds buffer size  "));
			ieat = iused;
			break;
		}

		// Incomplete message - must wait for more data.
		if (remain < message_length)
			break;

        // kharmon.  11-15-06.  Zero out buffers before using them.
	    msglen = 0;
	    memset(msgbuf, 0, sizeof(msgbuf));

		// Well-formed, but not necessarily valid, message.
		decode_length = decode(src + 2, msgbuf, message_length - 2);
		ieat	+= message_length;
		remain	-= message_length;
		src		+= message_length;

		if (!loggedOn)
		{
			switch (msgbuf[0])
			{
				case 'L':
					rc = handleAnsiLoginMsg();
					break;

				case 'M':
					switch (msgbuf[1])
					{
						case 'L':
							rc = handleUnicodeLoginMsg();
							break;
						default:
							rc = handleUnknownMsg();
							break;

					}
					break;

#ifdef ALLOW_SPIN_TESTING
				case 'T':
					if ( ServerOpts::bAllowTesting )
					{
						rc = handleSpinTestMessage();
					}
					else
					{
						rc = handleUnknownMsg();
					}
					break;
#endif

				default:
					rc = handleUnknownMsg();
					break;
			}
		}

		else // logged on
		{
			switch (msgbuf[0])
			{
				case 'B':
					rc = handleClientDataMsg();
					break;

				case 'G':
					// [AO 2008-01-16] HA: verify the user is still logged on
					if (!ValidateSession())
						rc = true;
					else
						rc = handleSubgameMsg();
					break;

				case 'J':
					// [AO 2007-10-01] Ignore repeating messages
					// [AO 2008-01-16] HA: verify the user is still logged on
					if (msgbuf[0] == last_message || !ValidateSession())
						rc = true;
					else
						rc = handleJackpotMsg();
					break;

				case 'S':
					rc = handleSpinMsg();
					break;

				case 'm':  // Balance Request
					// balance Broadcast System (BBS)
					// client needs fresh balance to synchronize balance display

					// [AO 2007-10-01] Ignore repeating messages
					// [AO 2008-01-16] HA: verify the user is still logged on
					if (msgbuf[0] == last_message || !ValidateSession())
						rc = true;
					else
						rc = handleGetBalance();
					break;

				case 'E':
					rc = handleClientErrorMsg();
					break;

				default:
					rc = handleUnknownMsg();
					break;
			}
		}

		last_message = msgbuf[0]; // [AO 2007-10-01] Handle repeating messages
    }

	if (iused <= ieat) // [AO 2007-10-01] Avoid buffer overflow
		iused = 0;
	else
	{
		MoveMemory(ibuf, ibuf + ieat, iused - ieat);
		iused -= ieat;
	}

	// NOTE:
	//   The value returned by the *last* message handler is used as our
	//   return value.  This is because the last message handler has either
	//   a message queued in msgbuf (true), or has ready sent msgbuf (false).
	//   Thus, the last method is always our authoritative source.
	return rc;
}

int32
SlotConnection::creditsToCents(int32 cred)
{
    return cred * info->betperline;
}

// truncates (floor) value since fractional credits not allowed
//
int32
SlotConnection::centsToCredits(int32 cents)
{
    return cents / info->betperline;
}

// balance Broadcast System (BBS)
bool8
SlotConnection::handleGetBalance()
{
	if (isServerStopping())
	{
		sendStopMsg();
		return true;
	}

	//rule1: gotta be logged on
    if( loggedOn && trackGetBalance() )
    {
        msgStart();
        SendUint8('m');
        SendInt32(info->balance);
        msgEnd();
    }

	//success
    return true;
}

void    
SlotConnection::allocateXMLTranslator( XMLTranslator** target )
{
	*target = new AUSlotsXMLTranslator( getServer(), this );
}

// [AO 2008-01-16] Refactoring for HA
bool SlotConnection::ValidateSession()
{
	int32 session_check = trackValidateSession();
	if (!session_check) // session is valid
		return true;

	// [AO 2008-01-22] Additional case from RTG 6.6.0 code drop
	if (session_check == SlotsSQL::NormalLogout)
		session_check = LOGINERR_NOTLOGGEDIN;

	msgStart();
	msgAdd(static_cast<uint8>('N'));
	msgAdd(static_cast<int32>(session_check));
	msgEnd();
	return false;
}
